
;(function(){

function isFunction(v){ return typeof(v) == 'function'; }
function isArray(v){ return Object.prototype.toString.call(v) == '[object Array]'; }
function isString(v){ return typeof(v) == 'string'; }

function SEND(url, data, header, fn){
	var xhr = new XMLHttpRequest();
	xhr.open('POST', url, true);
	xhr.addEventListener('load', function load(){
		this.removeEventListener('load', load);
		var result = xhr.response || xhr.responseText;
		if(typeof(result) == 'string'){
			try{
				result = JSON.parse(result);
			} catch(ex){ }
		}
		fn(result);
		xhr = null;
	});
	if(header){
		for(var p in header){
			xhr.setRequestHeader(p, header[p]);
		}
	}
	xhr.send(data || null);
};

function BlobReader(blob){
	this.blob = blob;
	this.position = 0;
};
BlobReader.prototype = {
	info: function(){
		return {
			size: this.blob.size || null,
			type: this.blob.type || null,
			name: this.blob.name || null,
			lastModified: this.blob.lastModified || null
		}
	},
	read: function(size, index){
		index = index || this.position;
		size = size || 512;
		var blob = this.blob.slice(index, index + size);
		this.position = index + blob.size;
		return blob;
	},
	eof: function(){
		return this.position >= this.blob.size;
	},
	dispose: function(){
		delete this.blob;
		delete this.position;
	},
	ok: function(){ return this.blob && !this.eof(); }
};
function Marker(names, initState){
	this.names = isArray(names) ? names : [];
	this.value = initState || 0;
};
Marker.prototype = {
	getPos: function(pos){
		return isString(pos) ? this.names.indexOf(pos) : (pos < 0 ? 0 : (pos || 0)) >> 0;
	},
	get: function(pos){
		pos = this.getPos(pos);
		return (this.value & (1<<pos))>>pos;
	},
	set: function(pos, yes){
		pos = this.getPos(pos);
		var v = 1 << pos;
		if(yes){
			this.value |= v;
		} else {
			var mask = (1 << (this.len() + 1)) - 1;
			mask ^= v;
			this.value &= mask;
		}
	},
	should: function(pos, yes){
		pos = this.getPos(pos);
		yes = !yes ? 0 : 1;
		var val = this.get(pos);
		return yes ^ val;
	},
	len: function(){
		return this.toString().length;
	},
	toString: function(){
		return this.value.toString(2);
	}
};

function AsyncReader(reader, asyncMethod, asyncCallback, asyncContext, arg){
	asyncCallback = asyncCallback.bind(asyncContext = asyncContext || null);
	return function(){
		asyncMethod(asyncCallback, reader, arg);
	}
};
function InvokeEvent(context, name){
	var evtName = 'on' + name;
	var args = Array.prototype.slice.call(arguments, 2);
	if(isFunction(context[evtName])){
		args.push(name);
		context[evtName].apply(context, args);
	} else {
		isFunction(context.onFireEvent) && context.onFireEvent(name, evtName, args);
	}
};

function AsyncBlob(blob, asyncMethod, asyncCallback, asyncInit){
	var reader = new BlobReader(blob);
	this.id = encrypt.getId();
	this.info = reader.info();
	this.err_msg = null;
	this.err_code = 0;
	this.marker = new Marker(['ready', 'progress', 'pause', 'load', 'error']);
	this.async = AsyncReader(reader
		, asyncMethod
		, asyncCallback
		, this
		, isFunction(asyncInit) ? asyncInit(reader) : null);
};
AsyncBlob.prototype = {
	error: function(code, msg){
		if(this.marker.get('error')){
			InvokeEvent(this, 'error', this.err_code, this.err_msg, this.info);
			return;
		}
		this.marker.set('error', 1);
		this.err_code = code;
		this.err_msg = msg || null;
	},
	start: function(){
		if(this.marker.get('error')){
			InvokeEvent(this, 'error', this.err_code, this.err_msg, this.info);
			return;
		}
		if(this.marker.get('ready')) return;
		this.marker.set('ready', 1);
		this.marker.set('load', 0);
		this.marker.set('progress', 1);
		this.async();
	},
	pause: function(){
		if(this.marker.get('error')){
			InvokeEvent(this, 'error', this.err_code, this.err_msg, this.info);
			return;
		}
		if(!this.marker.get('load')
			&& this.marker.get('progress')
			&& this.marker.get('ready')
			&& this.marker.should('pause', 1)
			){
			this.marker.set('pause', 1);
		}
	},
	resume: function(){
		if(this.marker.get('error')){
			InvokeEvent(this, 'error', this.err_code, this.err_msg, this.info);
			return;
		}
		if(!this.marker.get('load')
			&& this.marker.should('pause', 0)){
			this.marker.set('pause', 0);
			this.marker.set('progress', 1);
			InvokeEvent(this, 'resume');
			this.async();
		}
	}
};

function HashComputer(blob, blockSize, configure){
	blockSize = blockSize > 0 ? blockSize : 10240;
	var r = new AsyncBlob(blob,
		function(fn, reader, helper){
			var r = new FileReader();
			r.addEventListener('load', function load(e){
				this.removeEventListener('load', load);
				var arr = new Uint8Array(this.result);
				isFunction(fn) && fn(arr, helper, reader);
				r = null;
			});
			r.readAsArrayBuffer(reader.read(blockSize));
		},
		function(arr, helper, reader){
			var end = reader.eof();

			helper.hash(arr);
			end && helper.fin(arr.length);
			helper.cal();

			arr = null;

			InvokeEvent(this, 'progress', reader.position, reader.blob.size, reader.blob.name);

			if(end){
				this.marker.set('progress', 0);
				this.marker.set('load', 1);
				InvokeEvent(this, 'load', helper.hex(), helper.type, this.info);
				helper.dispose();
				reader.dispose();
				helper = reader = null;
				return;
			} else {
				if(this.marker.get('pause')){
					this.marker.set('progress', 0);
					InvokeEvent(this, 'pause');
				} else {
					this.async();
				}
			}
		},
		function(){
			return encrypt.createHelper(configure.HASHTYPE || 'sha1', blob.size);
		}
	);
	if(configure.MAXFILESIZE < blob.size){
		r.error(1000, 'The file size should be less than or equal ' + configure.MAXFILESIZE);
	}
	return r;
};

var UNLOAD_GUID = [];
function ADD_GUID(guid){
	UNLOAD_GUID.push(guid);
};
function RMV_GUID(guid){
	var idx = UNLOAD_GUID.indexOf(guid);
	if(idx > -1){
		UNLOAD_GUID.splice(idx, 1);
	}
};
function SVR_CLEAR(url, header){
	SEND(url, UNLOAD_GUID.join(','), header, function(r){
		console.log(r)
	});
	UNLOAD_GUID = [];
};
function Uploader(blob, hash, configure, url){
	blockSize = configure.CONFIG.PKGSIZE > 0 ? configure.CONFIG.PKGSIZE : 10240;
	hash = hash || null;
	var header = {};
	var guid = null;
	var data = null;
	var cmdName = null;
	var cmd = 0;
	var delay = configure.CONFIG.CLIENTDELAY;
	
	var r = new AsyncBlob(blob,
		function(fn, reader){
			SEND(url, data, header, function(result){
				setTimeout(function(){
					isFunction(fn) && fn(result, header, reader);
				}, delay);
			});
		},
		function(result, header, reader){

			// console.log(result)

			if(this.marker.get('pause')){
				this.marker.set('progress', 0);
				InvokeEvent(this, 'pause');
				return;
			}


			switch(result.status){
				case 200:
					switch(result.msg){
						case 'ready':
							cmdName = result.header;
							cmd = result.next;
							header[cmdName] = cmd;
							this.async();
							break;
						case 'config':
							cmd = result.next;
							configure = result.data;
							header[cmdName] = cmd;
							header[configure.MSG.HASH] = hash;
							var info = reader.info();
							info.hash = hash;
							data = JSON.stringify(info);
							this.async();
							break;
						case 'shakehand':
							InvokeEvent(this, 'progress', reader.position, reader.blob.size, reader.blob.name);
							guid = result.guid;
							ADD_GUID(guid);
							cmd = configure.CMD.UPLOADING;
							header[configure.MSG.GUID] = guid;
							header[configure.MSG.HASH] = hash;
							header[cmdName] = cmd;
							data = reader.read(blockSize);
							this.async();
							break;
						case 'uploading':
							InvokeEvent(this, 'progress', reader.position, reader.blob.size, reader.blob.name);
							if(reader.eof()){
								cmd = configure.CMD.UPLOAD;
								header[configure.MSG.GUID] = guid;
								header[configure.MSG.HASH] = hash;
								header[cmdName] = cmd;
								data = null;
							} else {
								cmd = configure.CMD.UPLOADING;
								header[configure.MSG.GUID] = guid;
								header[configure.MSG.HASH] = hash;
								header[cmdName] = cmd;
								data = reader.read(blockSize);
							}
							this.async();
							break;
						case 'upload':
							InvokeEvent(this, 'progress', reader.blob.size, reader.blob.size, reader.blob.name);
							InvokeEvent(this, 'load', result.guid, result.hash, this.info);
							break;
						default:
							for(var p in header){
								delete header[p];
							}
							break;
					}
					break;
				case 203:
					switch(result.msg){
						case 'upload':
							InvokeEvent(this, 'progress', reader.blob.size, reader.blob.size, reader.blob.name);
							InvokeEvent(this, 'load', result.guid, result.hash, this.info, result.status);
							break;
					}
					break;
			}
			
		},
		function(reader){
			if(configure){
				cmdName = configure.HEADER;
				cmd = configure.NEXT;
				header[cmdName] = cmd;
				header[configure.MSG.HASH] = hash;
				switch(cmd){
					case configure.CMD.SHAKEHAND:
						var info = reader.info();
						info.hash = hash;
						data = JSON.stringify(info);
						break;
				}
			}
		}
	);
	if(configure.MAXFILESIZE < blob.size){
		r.error(1000, 'The file size should be less than or equal ' + configure.MAXFILESIZE);
	}
	return r;
};

var ID = 0;
function CREATE(file, events, target){
	events = events || {};
	target = target || null;
	var id = ++ID;
	var _ = this;
	var url = _.url;
	var configure = _.CONFIG;
	var header = _.HEADER;
	var next = _.NEXT;
	var th = HashComputer(file, 65536 * 16, configure);
	th.onFireEvent = function(name, evtName, args){
		if(isFunction(events[evtName])){
			args.unshift('hash');
			args.unshift(id);
			events[evtName].apply(target, args);
		}
	};
	th.onload = function(hash, type){
		th = Uploader(file, hash, _, url);
		th.onFireEvent = function(name, evtName, args){
			if(isFunction(events[evtName])){
				args.unshift('upload');
				args.unshift(id);
				events[evtName].apply(target, args);
			}
		};
		th.start();
	};
	return {
		id: ++ID,
		start: function(){
			th.start();
			return this;
		},
		pause: function(){
			th.pause();
			return this;
		},
		resume: function(){
			th.resume();
			return this;
		}
	};
};
function AjaxUploader(url){
	this.url = url;
	this.id = ++ID;
};
AjaxUploader.prototype = {
	connect: function(fn){
		SEND(this.url, null, null, function(r){
			if(r.err_code){
				this.error(r);
				return;
			}
			this.HEADER = r.header;
			var header = {};
			header[this.HEADER] = r.next;

			

			SEND(this.url, null, header, function(r){
				this.CONFIG = r.data.CONFIG;	
				this.CMD = r.data.CMD;
				this.MSG = r.data.MSG;
				this.NEXT = r.next;
				this.create = CREATE.bind(this);
				// console.log(this);
				// InvokeEvent(this, 'connect');

				var url = this.url;
				var clearHeader = {};
				clearHeader[this.HEADER] = this.CMD.CLEAR;
				
				// window.addEventListener('beforeunload', function onbeforeunload(e){
				// 	// if(UNLOAD_GUID.length > 0){
				// 	// 	e.returnValue = true;
				// 	// }
				// 	SVR_CLEAR(url, clearHeader);
				// });
				// window.addEventListener('unload', function unload(e){
				// 	SVR_CLEAR(url, clearHeader);
				// });

				// window.sc = function(){
				// 	SVR_CLEAR(url, clearHeader);
				// }
				console.log(this)
				isFunction(fn) && fn(r.next);
			}.bind(this));
			header = null;

		}.bind(this));
	}
};

window.AjaxUploader = AjaxUploader;



})()